package com.atguigu.gmall.item.service.impl;

import com.atguigu.gmall.feign.list.ListFeignClient;
import com.fasterxml.jackson.core.type.TypeReference;
import com.atguigu.gmall.common.config.cache.MyCacheService;
import com.atguigu.gmall.common.constants.RedisConst;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.util.JsonUtil;
import com.atguigu.gmall.feign.product.ProductFeignClient;
import com.atguigu.gmall.common.config.cache.aop.anntation.Cache;
import com.atguigu.gmall.item.service.SkuItemDetailService;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.model.product.SpuSaleAttr;
import com.atguigu.gmall.model.to.SkuDetailTo;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author A
 * @version 1.0
 * @description: TODO
 * @date 2024/9/11 5:18
 */

@Slf4j
@Service
public class SkuItemDetailServiceImpl implements SkuItemDetailService {
    @Autowired
    ProductFeignClient productFeignClient;
    @Autowired
    ThreadPoolExecutor poolExecutor;
    @Autowired
    MyCacheService myCacheService;
    @Qualifier("skuBloomFilter")
    @Autowired
    RBloomFilter skuBloomFilter;
    @Autowired
    StringRedisTemplate redisTemplate;
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    ListFeignClient listFeignClient;

    @Cache(CacheKey = RedisConst.SKUDETAIL_CACHE_KEY + "#{#args[0]}", BloomName = "skuBloomFilter", bloomValue = "#{#args[0]}")
    @Override
    public SkuDetailTo getSkuDetail(Long skuId) {
        log.info("skuDetail:{} 回源查表", skuId);
        SkuDetailTo fromDB = getSkuDetailFromDB(skuId);
        return fromDB;
    }

    @Override
    public void increHotScore(Long skuId) {
        Double aDouble = redisTemplate.opsForZSet().incrementScore(RedisConst.SKU_HOTSCORE, skuId.toString(), 1);
        if (aDouble % 100 == 0) {
            listFeignClient.updateHotScore(skuId, aDouble.longValue());
        }
    }

    //用redisson的进阶方案getSkuDetailByRedisson
    public SkuDetailTo getSkuDetailByRedisson(Long skuId) {
        String cacheKey = RedisConst.SKUDETAIL_CACHE_KEY + skuId;
        String lockeKey = RedisConst.SKUDETAIL_LOCK_PREFIX + skuId;
        //查缓存
        SkuDetailTo cacheData = myCacheService.getCacheData(cacheKey, new TypeReference<SkuDetailTo>() {
        });
        //是否有缓存
        if (cacheData != null) {
            log.info("skuDetail:{} 命中缓存...", skuId);
            return cacheData;
        } else {
            log.info("skuDetail:{} 缓存未命中...,开始查询布隆过滤器", skuId);
            if (skuBloomFilter.contains(skuId)) {
                log.info("skuDetail:{} 布隆过滤器存在该id,回源查表", skuId);
                RLock lock = redissonClient.getLock(lockeKey);
                log.info("skuDetail:{} 回源查表前创建锁", skuId);
                //自动设置过期时间30s,也自动续期(看门狗时间30/3,到时间自动续期)
                boolean flag = lock.tryLock();
                try {
                    if (flag) {
                        log.info("skuDetail:{} 回源查表前抢到锁了", skuId);
                        //回源查表要加锁
                        SkuDetailTo fromDB = getSkuDetailFromDB(skuId);
                        log.info("skuDetail:{} 回源查表拿到结果了", skuId);
                        myCacheService.save(cacheKey, fromDB);
                        log.info("skuDetail:{} 将结果存到缓存中", skuId);
                        return fromDB;
                    } else {
                        log.info("skuDetail:{} 回源查表前没有抢到锁", skuId);
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                        log.info("skuDetail:{} 没有抢到锁,睡1s后从缓存中查", skuId);
                        return myCacheService.getCacheData(cacheKey, new TypeReference<SkuDetailTo>() {
                        });

                    }
                } finally {
                    try {
                        if (flag) {
                            lock.unlock();
                            log.info("skuDetail:{} 回源查表后解锁成功", skuId);
                        }
                        log.info("skuDetail:{} 没有锁资源,不用解锁", skuId);
                    } catch (Exception e) {
                        log.error("又解到别人的锁了", e);
                    }
                }

            } else {
                log.info("skuDetail:{} 布隆过滤器不存在该id,直接打回请求", skuId);
                return null;
            }
        }


    }

    //用redis原生的分布式锁解决方案
    public SkuDetailTo getSkuDetailByRedis(Long skuId) {
        String cacheKey = RedisConst.SKUDETAIL_CACHE_KEY + skuId;
        String lockeKey = RedisConst.LOCK_PREFIX + skuId;

        //1.先查缓存
        SkuDetailTo cacheData = myCacheService.getCacheData(cacheKey, new TypeReference<SkuDetailTo>() {
        });
        //2.缓存为空
        if (cacheData == null) {
            log.info("缓存未命中: {}...开始查询布隆过滤器", skuId);
            //3.查询布隆过滤器
            if (!skuBloomFilter.contains(skuId)) {
                log.info("布隆过滤器未存储该skuId: {}...打回", skuId);

                return null;
            } else {
                log.info("布隆过滤器存在该skuId: {}", skuId);
                String token = UUID.randomUUID().toString();
                //锁的粒度太粗,加上skuId粒度更细
                //加锁要设置过期时间.并保持原子性,还要保证锁的唯一性(防止删错)
                Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockeKey, token, 5, TimeUnit.MINUTES);
                SkuDetailTo skuDetailTo;
                if (Boolean.TRUE.equals(lock)) {

                    try {
                        log.info("抢到分布式锁了...准备回源查询: {}", skuId);
                        skuDetailTo = getSkuDetailFromDB(skuId);
                        log.info("回表查询到:{},并存储到缓存中", skuId);
                        myCacheService.save(cacheKey, skuDetailTo);
                    } finally {
                        //删锁也要保证原子性
                        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                        Long executed = redisTemplate.execute(new DefaultRedisScript<>(script, Long.class), Arrays.asList(lockeKey), token);
                        if (executed == 1) {
                            log.info("这是我的锁,删除分布式锁成功...: {}", skuId);
                        } else {
                            log.info("这是别人的锁,删除分布式锁失败...: {}", skuId);
                        }

                    }

                } else {
                    log.info("未抢到分布式锁... 睡1s然后查缓存: {}", skuId);
                    try {
                        Thread.sleep(1000);
                        return myCacheService.getCacheData(cacheKey, new TypeReference<SkuDetailTo>() {
                        });
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                }
                return skuDetailTo;
            }

        }
        log.info("命中缓存: {}..查询缓存", skuId);

        //缓存中有
        return cacheData;
    }

    //查库操作
    public SkuDetailTo getSkuDetailFromDB(Long skuId) {
        SkuDetailTo skuDetailTo = new SkuDetailTo();

        CompletableFuture<Void> categoryFuture = CompletableFuture.runAsync(() -> {
            //查分类
            Result<BaseCategoryView> baseCategoryViewResult = productFeignClient.getBaseCategoryView(skuId);

            if (baseCategoryViewResult.isOk()) {
                skuDetailTo.setCategoryView(baseCategoryViewResult.getData());
            }
        }, poolExecutor);

        CompletableFuture<Void> skuInfoFuture = CompletableFuture.runAsync(() -> {
            //2.查sku信息
            Result<SkuInfo> skuInfo = productFeignClient.getSkuInfo(skuId);
            if (skuInfo.isOk()) {
                skuDetailTo.setSkuInfo(skuInfo.getData());
            }
        }, poolExecutor);
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            //3.查价格
            Result<BigDecimal> price = productFeignClient.getPrice(skuId);

            if (price.isOk()) {
                skuDetailTo.setPrice(price.getData());
            }
        }, poolExecutor);
        CompletableFuture<Void> spuSaleAttrValuesFuture = CompletableFuture.runAsync(() -> {
            //4.查所有销售属性集合
            Result<List<SpuSaleAttr>> spuSaleAttrValues = productFeignClient.getSpuSaleAttrValues(skuId);
            if (spuSaleAttrValues.isOk()) {
                skuDetailTo.setSpuSaleAttrList(spuSaleAttrValues.getData());
            }
        }, poolExecutor);
        CompletableFuture<Void> skuValueJsonFuture = CompletableFuture.runAsync(() -> {
            //5.查实际sku组合
            Result<Map<String, String>> skuValueJson = productFeignClient.getSkuValueJson(skuId);
            skuDetailTo.setValuesSkuJson(JsonUtil.obj2Json(skuValueJson.getData()));
        }, poolExecutor);

        CompletableFuture.allOf(categoryFuture, skuInfoFuture, priceFuture, spuSaleAttrValuesFuture, skuValueJsonFuture).join();
        return skuDetailTo;
    }


}
